/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import { KeyCode, KeyCodeUtils, KeyMod, SimpleKeybinding } from '../common/keyCodes';
import * as platform from '../common/platform';

let KEY_CODE_MAP: { [keyCode: number]: KeyCode } = new Array(230);
let INVERSE_KEY_CODE_MAP: KeyCode[] = new Array(KeyCode.MAX_VALUE);

(function () {
    for (let i = 0; i < INVERSE_KEY_CODE_MAP.length; i++) {
        INVERSE_KEY_CODE_MAP[i] = -1;
    }

    function define(code: number, keyCode: KeyCode): void {
        KEY_CODE_MAP[code] = keyCode;
        INVERSE_KEY_CODE_MAP[keyCode] = code;
    }

    define(3, KeyCode.PauseBreak); // VK_CANCEL 0x03 Control-break processing
    define(8, KeyCode.Backspace);
    define(9, KeyCode.Tab);
    define(13, KeyCode.Enter);
    define(16, KeyCode.Shift);
    define(17, KeyCode.Ctrl);
    define(18, KeyCode.Alt);
    define(19, KeyCode.PauseBreak);
    define(20, KeyCode.CapsLock);
    define(27, KeyCode.Escape);
    define(32, KeyCode.Space);
    define(33, KeyCode.PageUp);
    define(34, KeyCode.PageDown);
    define(35, KeyCode.End);
    define(36, KeyCode.Home);
    define(37, KeyCode.LeftArrow);
    define(38, KeyCode.UpArrow);
    define(39, KeyCode.RightArrow);
    define(40, KeyCode.DownArrow);
    define(45, KeyCode.Insert);
    define(46, KeyCode.Delete);

    define(48, KeyCode.KEY_0);
    define(49, KeyCode.KEY_1);
    define(50, KeyCode.KEY_2);
    define(51, KeyCode.KEY_3);
    define(52, KeyCode.KEY_4);
    define(53, KeyCode.KEY_5);
    define(54, KeyCode.KEY_6);
    define(55, KeyCode.KEY_7);
    define(56, KeyCode.KEY_8);
    define(57, KeyCode.KEY_9);

    define(65, KeyCode.KEY_A);
    define(66, KeyCode.KEY_B);
    define(67, KeyCode.KEY_C);
    define(68, KeyCode.KEY_D);
    define(69, KeyCode.KEY_E);
    define(70, KeyCode.KEY_F);
    define(71, KeyCode.KEY_G);
    define(72, KeyCode.KEY_H);
    define(73, KeyCode.KEY_I);
    define(74, KeyCode.KEY_J);
    define(75, KeyCode.KEY_K);
    define(76, KeyCode.KEY_L);
    define(77, KeyCode.KEY_M);
    define(78, KeyCode.KEY_N);
    define(79, KeyCode.KEY_O);
    define(80, KeyCode.KEY_P);
    define(81, KeyCode.KEY_Q);
    define(82, KeyCode.KEY_R);
    define(83, KeyCode.KEY_S);
    define(84, KeyCode.KEY_T);
    define(85, KeyCode.KEY_U);
    define(86, KeyCode.KEY_V);
    define(87, KeyCode.KEY_W);
    define(88, KeyCode.KEY_X);
    define(89, KeyCode.KEY_Y);
    define(90, KeyCode.KEY_Z);

    define(93, KeyCode.ContextMenu);

    define(96, KeyCode.NUMPAD_0);
    define(97, KeyCode.NUMPAD_1);
    define(98, KeyCode.NUMPAD_2);
    define(99, KeyCode.NUMPAD_3);
    define(100, KeyCode.NUMPAD_4);
    define(101, KeyCode.NUMPAD_5);
    define(102, KeyCode.NUMPAD_6);
    define(103, KeyCode.NUMPAD_7);
    define(104, KeyCode.NUMPAD_8);
    define(105, KeyCode.NUMPAD_9);
    define(106, KeyCode.NUMPAD_MULTIPLY);
    define(107, KeyCode.NUMPAD_ADD);
    define(108, KeyCode.NUMPAD_SEPARATOR);
    define(109, KeyCode.NUMPAD_SUBTRACT);
    define(110, KeyCode.NUMPAD_DECIMAL);
    define(111, KeyCode.NUMPAD_DIVIDE);

    define(112, KeyCode.F1);
    define(113, KeyCode.F2);
    define(114, KeyCode.F3);
    define(115, KeyCode.F4);
    define(116, KeyCode.F5);
    define(117, KeyCode.F6);
    define(118, KeyCode.F7);
    define(119, KeyCode.F8);
    define(120, KeyCode.F9);
    define(121, KeyCode.F10);
    define(122, KeyCode.F11);
    define(123, KeyCode.F12);
    define(124, KeyCode.F13);
    define(125, KeyCode.F14);
    define(126, KeyCode.F15);
    define(127, KeyCode.F16);
    define(128, KeyCode.F17);
    define(129, KeyCode.F18);
    define(130, KeyCode.F19);

    define(144, KeyCode.NumLock);
    define(145, KeyCode.ScrollLock);

    define(186, KeyCode.US_SEMICOLON);
    define(187, KeyCode.US_EQUAL);
    define(188, KeyCode.US_COMMA);
    define(189, KeyCode.US_MINUS);
    define(190, KeyCode.US_DOT);
    define(191, KeyCode.US_SLASH);
    define(192, KeyCode.US_BACKTICK);
    define(193, KeyCode.ABNT_C1);
    define(194, KeyCode.ABNT_C2);
    define(219, KeyCode.US_OPEN_SQUARE_BRACKET);
    define(220, KeyCode.US_BACKSLASH);
    define(221, KeyCode.US_CLOSE_SQUARE_BRACKET);
    define(222, KeyCode.US_QUOTE);
    define(223, KeyCode.OEM_8);

    define(226, KeyCode.OEM_102);

	/**
	 * https://lists.w3.org/Archives/Public/www-dom/2010JulSep/att-0182/keyCode-spec.html
	 * If an Input Method Editor is processing key input and the event is keydown, return 229.
	 */
    define(229, KeyCode.KEY_IN_COMPOSITION);

    define(91, KeyCode.Meta);
    if (platform.isMacintosh) {
        // the two meta keys in the Mac have different key codes (91 and 93)
        define(93, KeyCode.Meta);
    } else {
        define(92, KeyCode.Meta);
    }
})();

function extractKeyCode(e: KeyboardEvent): KeyCode {
    if (e.charCode) {
        // "keypress" events mostly
        let char = String.fromCharCode(e.charCode).toUpperCase();
        return KeyCodeUtils.fromString(char);
    }
    return KEY_CODE_MAP[e.keyCode] || KeyCode.Unknown;
}

export function getCodeForKeyCode(keyCode: KeyCode): number {
    return INVERSE_KEY_CODE_MAP[keyCode];
}

export interface IKeyboardEvent {
    readonly browserEvent: KeyboardEvent;
    readonly target: HTMLElement;

    readonly ctrlKey: boolean;
    readonly shiftKey: boolean;
    readonly altKey: boolean;
    readonly metaKey: boolean;
    readonly keyCode: KeyCode;
    readonly code: string;

	/**
	 * @internal
	 */
    toKeybinding(): SimpleKeybinding;
    equals(keybinding: number): boolean;

    preventDefault(): void;
    stopPropagation(): void;
}

const ctrlKeyMod = (platform.isMacintosh ? KeyMod.WinCtrl : KeyMod.CtrlCmd);
const altKeyMod = KeyMod.Alt;
const shiftKeyMod = KeyMod.Shift;
const metaKeyMod = (platform.isMacintosh ? KeyMod.CtrlCmd : KeyMod.WinCtrl);

export class StandardKeyboardEvent implements IKeyboardEvent {

    public readonly browserEvent: KeyboardEvent;
    public readonly target: HTMLElement;

    public readonly ctrlKey: boolean;
    public readonly shiftKey: boolean;
    public readonly altKey: boolean;
    public readonly metaKey: boolean;
    public readonly keyCode: KeyCode;
    public readonly code: string;

    private _asKeybinding: number;
    private _asRuntimeKeybinding: SimpleKeybinding;

    constructor(source: KeyboardEvent) {
        let e = source;

        this.browserEvent = e;
        this.target = <HTMLElement>e.target;

        this.ctrlKey = e.ctrlKey;
        this.shiftKey = e.shiftKey;
        this.altKey = e.altKey;
        this.metaKey = e.metaKey;
        this.keyCode = extractKeyCode(e);
        this.code = e.code;

        // console.info(e.type + ": keyCode: " + e.keyCode + ", which: " + e.which + ", charCode: " + e.charCode + ", detail: " + e.detail + " ====> " + this.keyCode + ' -- ' + KeyCode[this.keyCode]);

        this.ctrlKey = this.ctrlKey || this.keyCode === KeyCode.Ctrl;
        this.altKey = this.altKey || this.keyCode === KeyCode.Alt;
        this.shiftKey = this.shiftKey || this.keyCode === KeyCode.Shift;
        this.metaKey = this.metaKey || this.keyCode === KeyCode.Meta;

        this._asKeybinding = this._computeKeybinding();
        this._asRuntimeKeybinding = this._computeRuntimeKeybinding();

        // console.log(`code: ${e.code}, keyCode: ${e.keyCode}, key: ${e.key}`);
    }

    public preventDefault(): void {
        if (this.browserEvent && this.browserEvent.preventDefault) {
            this.browserEvent.preventDefault();
        }
    }

    public stopPropagation(): void {
        if (this.browserEvent && this.browserEvent.stopPropagation) {
            this.browserEvent.stopPropagation();
        }
    }

    public toKeybinding(): SimpleKeybinding {
        return this._asRuntimeKeybinding;
    }

    public equals(other: number): boolean {
        return this._asKeybinding === other;
    }

    private _computeKeybinding(): number {
        let key = KeyCode.Unknown;
        if (this.keyCode !== KeyCode.Ctrl && this.keyCode !== KeyCode.Shift && this.keyCode !== KeyCode.Alt && this.keyCode !== KeyCode.Meta) {
            key = this.keyCode;
        }

        let result = 0;
        if (this.ctrlKey) {
            result |= ctrlKeyMod;
        }
        if (this.altKey) {
            result |= altKeyMod;
        }
        if (this.shiftKey) {
            result |= shiftKeyMod;
        }
        if (this.metaKey) {
            result |= metaKeyMod;
        }
        result |= key;

        return result;
    }

    private _computeRuntimeKeybinding(): SimpleKeybinding {
        let key = KeyCode.Unknown;
        if (this.keyCode !== KeyCode.Ctrl && this.keyCode !== KeyCode.Shift && this.keyCode !== KeyCode.Alt && this.keyCode !== KeyCode.Meta) {
            key = this.keyCode;
        }
        return new SimpleKeybinding(this.ctrlKey, this.shiftKey, this.altKey, this.metaKey, key);
    }
}
